﻿using Furion;
using Furion.Authorization;
using Furion.DataEncryption;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.Primitives;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security.Claims;
using YFurion.Attributes;
using YFurion.Extensions;

namespace YFurion.Session.Extensions
{
    public static class SessionExtensions
    {
        public static string GetClaimValue(this IYSession session, string claimType)
        {
            Claim userIdClaim = App.HttpContext?.User.Claims.FirstOrDefault(c => c.Type == claimType);
            return userIdClaim?.Value;
        }

        public static SecurityTokenResult CreateToken(this IYSession session, long subjectId, Dictionary<string, object> claims = null)
        {
            (long expiredTimestamp, long expiredTime) = GetExpiredTime();
            if (claims == null)
            {
                claims = new Dictionary<string, object>();
            }
            if (!claims.ContainsKey(YClaimTypes.SubjectId))
            {
                claims.Add(YClaimTypes.SubjectId, subjectId);
            }
            string accessToken = JWTEncryption.Encrypt(claims, expiredTime);
            string refreshToken = JWTEncryption.GenerateRefreshToken(accessToken);
            SecurityTokenResult securityTokenResult = new SecurityTokenResult
            {
                ExpiresIn = expiredTimestamp,
                AccessToken = accessToken,
                RefreshToken = refreshToken,
                TokenType = "jwt"
            };
            AddTokenToResponseHeader(App.HttpContext, new SecurityTokenResult
            {
                AccessToken = accessToken,
                ExpiresIn = expiredTimestamp,
                RefreshToken = refreshToken
            });
            return securityTokenResult;
        }

        public static bool AutoRefreshToken(this IYSession session, DefaultHttpContext httpContext, int refreshTokenExpiredTime = 43200, string tokenPrefix = "Bearer ", long clockSkew = 5)
        {
            //获取旧的 刷新Token
            string refreshToken = JWTEncryption.GetJwtBearerToken(httpContext, "X-Authorization", tokenPrefix);
            // 如果验证有效并且refreshToken为空，则跳过刷新
            if (httpContext.User.Identity.IsAuthenticated && refreshToken.IsNullOrWhiteSpace()) return true;

            // 判断是否含有匿名特性
            if (httpContext.GetEndpoint()?.Metadata?.GetMetadata<AllowAnonymousAttribute>() != null) return true;

            // 获取旧的 Token
            string expiredToken = JWTEncryption.GetJwtBearerToken(httpContext, tokenPrefix: tokenPrefix);
            if (expiredToken.IsNullOrWhiteSpace() || refreshToken.IsNullOrWhiteSpace()) return false;

            //获取过期时间(元组前者是时间戳，后者是时间分钟数)
            (long expiredTimestamp, long expiredTime) = GetExpiredTime();
            // 交换新的 Token
            string accessToken = JWTEncryption.Exchange(expiredToken, refreshToken, expiredTime, App.GetOptions<JWTSettingsOptions>().ClockSkew ?? clockSkew);
            if (accessToken.IsNullOrWhiteSpace()) return false;

            // 读取新的 Token Clamis
            IEnumerable<Claim> claims = JWTEncryption.ReadJwtToken(accessToken)?.Claims;
            if (claims == null) return false;

            // 创建身份信息
            ClaimsIdentity claimIdentity = new ClaimsIdentity("AuthenticationTypes.Federation");
            claimIdentity.AddClaims(claims);
            ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimIdentity);

            // 设置 HttpContext.User
            httpContext.User = claimsPrincipal;
            httpContext.SignInAsync(claimsPrincipal);

            AddTokenToResponseHeader(httpContext, new SecurityTokenResult
            {
                AccessToken = accessToken,
                ExpiresIn = expiredTimestamp,
                RefreshToken = JWTEncryption.GenerateRefreshToken(accessToken, refreshTokenExpiredTime)
            });
            return true;
        }

        /// <summary>
        /// 获取过期时间
        /// </summary>
        /// <returns>元组前者是时间戳，后者是时间分钟数</returns>
        private static (long, long) GetExpiredTime()
        {
            long expiredTime = App.GetOptions<JWTSettingsOptions>().ExpiredTime.Value;
            long expiredTimestamp = DateTimeOffset.Now.AddMinutes(expiredTime).ToUnixTimeSeconds();
            return (expiredTimestamp, expiredTime);
        }

        private static void AddTokenToResponseHeader(HttpContext httpContext, SecurityTokenResult securityTokenResult)
        {
            string accessTokenKey = "access-token", expiresInKey = "expires-in", xAccessTokenKey = "x-access-token", accessControlExposeKey = "Access-Control-Expose-Headers";
            httpContext.Response.Headers.TryGetValue(accessControlExposeKey, out StringValues acehs);
            httpContext.Response.Headers[accessControlExposeKey] = string.Join(',', StringValues.Concat(acehs, new StringValues(new[] { accessTokenKey, expiresInKey, xAccessTokenKey })).Distinct());
            // 返回新的 Token
            httpContext.Response.Headers[accessTokenKey] = securityTokenResult.AccessToken;
            // 返回新的 Token有效期
            httpContext.Response.Headers[expiresInKey] = securityTokenResult.ExpiresIn.ToString();
            // 返回新的 刷新Token
            httpContext.Response.Headers[xAccessTokenKey] = securityTokenResult.RefreshToken;
        }

        public static string[] GetPermissionCodes(this IYSession session, ControllerActionDescriptor controllerActionDescriptor, HttpContext httpContext)
        {
            string controllerName = controllerActionDescriptor.ControllerName;
            string method = httpContext.Request.Method;
            if (method == "GET")
            {
                return new string[] { controllerName };
            }

            CustomAuthorizeAttribute customAuthorizeAttribute = controllerActionDescriptor.MethodInfo.GetCustomAttribute<CustomAuthorizeAttribute>();
            if (customAuthorizeAttribute?.CodeArray.Length > 0)
            {
                string[] codeArray = customAuthorizeAttribute.CodeArray;
                string[] permissionCodes = new string[codeArray.Length];
                for (int i = 0; i < codeArray.Length; ++i)
                {
                    permissionCodes[i] = controllerName + codeArray[i];
                }
                return permissionCodes;
            }

            string actionName = controllerActionDescriptor.ActionName;
            if (actionName.IsNullOrWhiteSpace())
            {
                actionName = method.ToLower().ToPascalCase();
            }
            return new string[] { controllerName + actionName };
        }
    }
}
